﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Reflection;
using System.Collections.Specialized;
using System.Collections;
using System.Data.Common;
using System.Data;

namespace Normal
{
    /// <summary>
    /// This class is a proxy to intercept fields and method calls
    /// in order to implement the "On Demmand" SQL calls
    /// </summary>
    sealed class NormalProxy : RealProxy
    {
        private object _instance;
        private string _connectionString;
        private string _providerName;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="obj">Object Instance To "Proxy"</param>
        /// <param name="type">Type of the Instance</param>
        private NormalProxy(object obj, Type type, string ConnectionString,
            string ProviderName)
            : base(type)
        {
            this._instance = obj;
            this._connectionString = ConnectionString;
            this._providerName = ProviderName;
        }

        /// <summary>
        /// This override method is called whenever each property or method
        /// is called
        /// </summary>
        /// <param name="msg">The Proxy Message</param>
        /// <returns>The Message to be processed</returns>
        public override IMessage Invoke(IMessage msg)
        {
            MethodCallMessageWrapper mc = new MethodCallMessageWrapper((IMethodCallMessage)msg);

            MethodInfo method = mc.MethodBase as MethodInfo;

            object res = method.Invoke(
                this._instance
              , ((method.Attributes & MethodAttributes.SpecialName) == MethodAttributes.SpecialName) ? mc.InArgs : mc.Args);

            if (res == null && method.Name.StartsWith("get_") && method.ReturnType.Name.StartsWith("List"))
            {
                Type tpList = method.ReturnType.GetGenericArguments()[0];
                DbProviderFactory f = DbProviderFactories.GetFactory(_providerName);
                DbConnection cnn = f.CreateConnection();
                cnn.ConnectionString = _connectionString;
                cnn.Open();
                DbCommand cmdList = f.CreateCommand();
                cmdList.Connection = cnn;
                cmdList.CommandType = CommandType.Text;
                string sSQL = "Select ";
                string sWhere = "";
                foreach (PropertyInfo info in _instance.GetType().GetProperties())
                {
                    foreach (object attr in info.GetCustomAttributes(typeof(Id), true))
                    {
                        object idFather = _instance.GetType().InvokeMember(info.Name,
                            BindingFlags.GetProperty, null, _instance, null);
                        foreach (PropertyInfo inf2 in tpList.GetProperties())
                        {
                            foreach (object attr2 in inf2.GetCustomAttributes(typeof(Id), true))
                            {
                                sSQL += ((Id)attr2).Name + ", ";
                                if (inf2.PropertyType.ToString() == _instance.GetType().ToString())
                                {
                                    DbParameter par = f.CreateParameter();
                                    par.ParameterName = "@" + info.Name;
                                    par.Value = idFather;
                                    sWhere += ((Id)attr2).Name + " = @" + info.Name;
                                    cmdList.Parameters.Add(par);
                                }
                            }
                            foreach (object attr2 in inf2.GetCustomAttributes(typeof(Column), true))
                            {
                                if (inf2.PropertyType.ToString() == _instance.GetType().ToString())
                                {
                                    DbParameter par = f.CreateParameter();
                                    par.ParameterName = "@" + info.Name;
                                    par.Value = idFather;
                                    sWhere += ((Column)attr2).Name + " = @" + info.Name;
                                    cmdList.Parameters.Add(par);
                                }
                            }
                        }
                    }
                    sSQL = sSQL.Substring(0, sSQL.Length - 2);
                    foreach (object attr in tpList.GetCustomAttributes(typeof(Table), true))
                    {
                        sSQL += " From " + ((Table)attr).TableName;
                    }
                    sSQL += " Where " + sWhere;
                    break;
                }
                res = CreateGenericList(tpList);
                cmdList.CommandText = sSQL;
                DbDataReader tb = cmdList.ExecuteReader();
                DAO<object> dao = new DAO<object>(_connectionString, _providerName);
                while (tb.Read())
                {
                    List<object> lstKeys = new List<object>();
                    for (int x = 0; x < tb.FieldCount; x++)
                    {
                        lstKeys.Add(tb.GetValue(x));
                    }
                    object item = dao.FindById(lstKeys.ToArray(), tpList, null);
                    res.GetType().InvokeMember("Add", BindingFlags.InvokeMethod, null, 
                        res, new object[] { item });
                }
                tb.Close();
                cnn.Close();
                tb = null;
                cnn = null;
            }

            return new ReturnMessage(res, mc.Args, mc.Args.Length, 
                mc.LogicalCallContext, mc);
        }

        protected static object CreateGenericList(params Type[] types)
        {
            string t = "System.Collections.Generic.List`" + types.Length;
            Type generic = Type.GetType(t).MakeGenericType(types);
            return Activator.CreateInstance(generic);
        }

        /// <summary>
        /// Creates a Proxy Instance based on a given instance
        /// </summary>
        /// <param name="instance">Instance to be passed</param>
        /// <returns>The "Proxied" instance</returns>
        public static object CreateProxy(object instance, string ConnectionString,
            string ProviderName)
        {
            //Only works if the object inherits from MarshalByRefObject
            //Otherwise will return the class that won't be proxied
            if (instance != null && instance is MarshalByRefObject)
            {
                return new NormalProxy(instance, instance.GetType(),
                    ConnectionString, ProviderName).GetTransparentProxy();
            }
            return instance;
        }
    }
}
